/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
/**
 * Constructs a new graph editor
 */
 Menus = function(editorUi)
 {
	 this.editorUi = editorUi;
	 this.menus = new Object();
	 this.init();
	 
	 // Pre-fetches checkmark image
	 if (!mxClient.IS_SVG)
	 {
		 new Image().src = this.checkmarkImage;
	 }
 };
 
 /**
	* Sets the default font family.
	*/
 Menus.prototype.defaultFont = 'Helvetica';
 
 /**
	* Sets the default font size.
	*/
 Menus.prototype.defaultFontSize = '12';
 
 /**
	* Sets the default font size.
	*/
 Menus.prototype.defaultMenuItems = ['file', 'edit', 'view', 'arrange', 'extras', 'help'];
 
 /**
	* Adds the label menu items to the given menu and parent.
	*/
 Menus.prototype.defaultFonts = ['Helvetica', 'Verdana', 'Times New Roman', 'Garamond', 'Comic Sans MS',
														 'Courier New', 'Georgia', 'Lucida Console', 'Tahoma'];
 
 /**
	* Adds the label menu items to the given menu and parent.
	*/
 Menus.prototype.init = function()
 {
	 var graph = this.editorUi.editor.graph;
	 var isGraphEnabled = mxUtils.bind(graph, graph.isEnabled);
 
	 this.customFonts = [];
	 this.customFontSizes = [];
 
	 this.put('fontFamily', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 var addItem = mxUtils.bind(this, function(fontname)
		 {
			 var tr = this.styleChange(menu, fontname, [mxConstants.STYLE_FONTFAMILY], [fontname], null, parent, function()
			 {
				 document.execCommand('fontname', false, fontname);
			 }, function()
			 {
				 graph.updateLabelElements(graph.getSelectionCells(), function(elt)
				 {
					 elt.removeAttribute('face');
					 elt.style.fontFamily = null;
					 
					 if (elt.nodeName == 'PRE')
					 {
						 graph.replaceElement(elt, 'div');
					 }
				 });
			 });
			 tr.firstChild.nextSibling.style.fontFamily = fontname;
		 });
		 
		 for (var i = 0; i < this.defaultFonts.length; i++)
		 {
			 addItem(this.defaultFonts[i]);
		 }
 
		 menu.addSeparator(parent);
		 
		 if (this.customFonts.length > 0)
		 {
			 for (var i = 0; i < this.customFonts.length; i++)
			 {
				 addItem(this.customFonts[i]);
			 }
			 
			 menu.addSeparator(parent);
			 
			 menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
			 {
				 this.customFonts = [];
				 this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
			 }), parent);
			 
			 menu.addSeparator(parent);
		 }
		 
		 this.promptChange(menu, mxResources.get('custom') + '...', '', mxConstants.DEFAULT_FONTFAMILY, mxConstants.STYLE_FONTFAMILY, parent, true, mxUtils.bind(this, function(newValue)
		 {
			 if (mxUtils.indexOf(this.customFonts, newValue) < 0)
			 {
				 this.customFonts.push(newValue);
				 this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
			 }
		 }));
	 })));
	 this.put('formatBlock', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 function addItem(label, tag)
		 {
			 return menu.addItem(label, null, mxUtils.bind(this, function()
			 {
				 // TODO: Check if visible
				 if (graph.cellEditor.textarea != null)
				 {
					 graph.cellEditor.textarea.focus();
							 document.execCommand('formatBlock', false, '<' + tag + '>');
				 }
			 }), parent);
		 };
		 
		 addItem(mxResources.get('normal'), 'p');
		 
		 addItem('', 'h1').firstChild.nextSibling.innerHTML = '<h1 style="margin:0px;">' + mxResources.get('heading') + ' 1</h1>';
		 addItem('', 'h2').firstChild.nextSibling.innerHTML = '<h2 style="margin:0px;">' + mxResources.get('heading') + ' 2</h2>';
		 addItem('', 'h3').firstChild.nextSibling.innerHTML = '<h3 style="margin:0px;">' + mxResources.get('heading') + ' 3</h3>';
		 addItem('', 'h4').firstChild.nextSibling.innerHTML = '<h4 style="margin:0px;">' + mxResources.get('heading') + ' 4</h4>';
		 addItem('', 'h5').firstChild.nextSibling.innerHTML = '<h5 style="margin:0px;">' + mxResources.get('heading') + ' 5</h5>';
		 addItem('', 'h6').firstChild.nextSibling.innerHTML = '<h6 style="margin:0px;">' + mxResources.get('heading') + ' 6</h6>';
		 
		 addItem('', 'pre').firstChild.nextSibling.innerHTML = '<pre style="margin:0px;">' + mxResources.get('formatted') + '</pre>';
		 addItem('', 'blockquote').firstChild.nextSibling.innerHTML = '<blockquote style="margin-top:0px;margin-bottom:0px;">' + mxResources.get('blockquote') + '</blockquote>';
	 })));
	 this.put('fontSize', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];
		 
		 var addItem = mxUtils.bind(this, function(fontsize)
		 {
			 this.styleChange(menu, fontsize, [mxConstants.STYLE_FONTSIZE], [fontsize], null, parent, function()
			 {
				 if (graph.cellEditor.textarea != null)
				 {
					 // Creates an element with arbitrary size 3
					 document.execCommand('fontSize', false, '3');
					 
					 // Changes the css font size of the first font element inside the in-place editor with size 3
					 // hopefully the above element that we've just created. LATER: Check for new element using
					 // previous result of getElementsByTagName (see other actions)
					 var elts = graph.cellEditor.textarea.getElementsByTagName('font');
					 
					 for (var i = 0; i < elts.length; i++)
					 {
						 if (elts[i].getAttribute('size') == '3')
						 {
							 elts[i].removeAttribute('size');
							 elts[i].style.fontSize = fontsize + 'px';
							 
							 break;
						 }
					 }
				 }
			 });
		 });
		 
		 for (var i = 0; i < sizes.length; i++)
		 {
			 addItem(sizes[i]);
		 }
 
		 menu.addSeparator(parent);
		 
		 if (this.customFontSizes.length > 0)
		 {
			 for (var i = 0; i < this.customFontSizes.length; i++)
			 {
				 addItem(this.customFontSizes[i]);
			 }
			 
			 menu.addSeparator(parent);
			 
			 menu.addItem(mxResources.get('reset'), null, mxUtils.bind(this, function()
			 {
				 this.customFontSizes = [];
			 }), parent);
			 
			 menu.addSeparator(parent);
		 }
		 
		 this.promptChange(menu, mxResources.get('custom') + '...', '(pt)', '12', mxConstants.STYLE_FONTSIZE, parent, true, mxUtils.bind(this, function(newValue)
		 {
			 this.customFontSizes.push(newValue);
		 }));
	 })));
	 this.put('direction', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 menu.addItem(mxResources.get('flipH'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false); }, parent);
		 menu.addItem(mxResources.get('flipV'), null, function() { graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false); }, parent);
		 this.addMenuItems(menu, ['-', 'rotation'], parent);
	 })));
	 this.put('align', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 menu.addItem(mxResources.get('leftAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_LEFT); }, parent);
		 menu.addItem(mxResources.get('center'), null, function() { graph.alignCells(mxConstants.ALIGN_CENTER); }, parent);
		 menu.addItem(mxResources.get('rightAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_RIGHT); }, parent);
		 menu.addSeparator(parent);
		 menu.addItem(mxResources.get('topAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_TOP); }, parent);
		 menu.addItem(mxResources.get('middle'), null, function() { graph.alignCells(mxConstants.ALIGN_MIDDLE); }, parent);
		 menu.addItem(mxResources.get('bottomAlign'), null, function() { graph.alignCells(mxConstants.ALIGN_BOTTOM); }, parent);
	 })));
	 this.put('distribute', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 menu.addItem(mxResources.get('horizontal'), null, function() { graph.distributeCells(true); }, parent);
		 menu.addItem(mxResources.get('vertical'), null, function() { graph.distributeCells(false); }, parent);
	 })));
	 this.put('layout', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 var promptSpacing = mxUtils.bind(this, function(defaultValue, fn)
		 {
			 var dlg = new FilenameDialog(this.editorUi, defaultValue, mxResources.get('apply'), function(newValue)
			 {
				 fn(parseFloat(newValue));
			 }, mxResources.get('spacing'));
			 this.editorUi.showDialog(dlg.container, 300, 80, true, true);
			 dlg.init();
		 });
		 
		 menu.addItem(mxResources.get('horizontalFlow'), null, mxUtils.bind(this, function()
		 {
			 var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);
			 
				 this.editorUi.executeLayout(function()
				 {
					 var selectionCells = graph.getSelectionCells();
					 layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
				 }, true);
		 }), parent);
		 menu.addItem(mxResources.get('verticalFlow'), null, mxUtils.bind(this, function()
		 {
			 var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
			 
				 this.editorUi.executeLayout(function()
				 {
					 var selectionCells = graph.getSelectionCells();
					 layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
				 }, true);
		 }), parent);
		 menu.addSeparator(parent);
		 menu.addItem(mxResources.get('horizontalTree'), null, mxUtils.bind(this, function()
		 {
			 var tmp = graph.getSelectionCell();
			 var roots = null;
			 
			 if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
			 {
				 if (graph.getModel().getEdgeCount(tmp) == 0)
				 {
					 roots = graph.findTreeRoots(graph.getDefaultParent());
				 }
			 }
			 else
			 {
				 roots = graph.findTreeRoots(tmp);
			 }
 
			 if (roots != null && roots.length > 0)
			 {
				 tmp = roots[0];
			 }
			 
			 if (tmp != null)
			 {
				 var layout = new mxCompactTreeLayout(graph, true);
				 layout.edgeRouting = false;
				 layout.levelDistance = 30;
				 
				 promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
				 {
					 layout.levelDistance = newValue;
					 
					 this.editorUi.executeLayout(function()
						 {
						 layout.execute(graph.getDefaultParent(), tmp);
						 }, true);
				 }));
			 }
		 }), parent);
		 menu.addItem(mxResources.get('verticalTree'), null, mxUtils.bind(this, function()
		 {
			 var tmp = graph.getSelectionCell();
			 var roots = null;
			 
			 if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
			 {
				 if (graph.getModel().getEdgeCount(tmp) == 0)
				 {
					 roots = graph.findTreeRoots(graph.getDefaultParent());
				 }
			 }
			 else
			 {
				 roots = graph.findTreeRoots(tmp);
			 }
 
			 if (roots != null && roots.length > 0)
			 {
				 tmp = roots[0];
			 }
			 
			 if (tmp != null)
			 {
				 var layout = new mxCompactTreeLayout(graph, false);
				 layout.edgeRouting = false;
				 layout.levelDistance = 30;
				 
				 promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
				 {
					 layout.levelDistance = newValue;
					 
					 this.editorUi.executeLayout(function()
						 {
						 layout.execute(graph.getDefaultParent(), tmp);
						 }, true);
				 }));
			 }
		 }), parent);
		 menu.addItem(mxResources.get('radialTree'), null, mxUtils.bind(this, function()
		 {
			 var tmp = graph.getSelectionCell();
			 var roots = null;
			 
			 if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
			 {
				 if (graph.getModel().getEdgeCount(tmp) == 0)
				 {
					 roots = graph.findTreeRoots(graph.getDefaultParent());
				 }
			 }
			 else
			 {
				 roots = graph.findTreeRoots(tmp);
			 }
 
			 if (roots != null && roots.length > 0)
			 {
				 tmp = roots[0];
			 }
			 
			 if (tmp != null)
			 {
				 var layout = new mxRadialTreeLayout(graph, false);
				 layout.levelDistance = 80;
				 layout.autoRadius = true;
				 
				 promptSpacing(layout.levelDistance, mxUtils.bind(this, function(newValue)
				 {
					 layout.levelDistance = newValue;
					 
					 this.editorUi.executeLayout(function()
						 {
							 layout.execute(graph.getDefaultParent(), tmp);
							 
							 if (!graph.isSelectionEmpty())
							 {
								 tmp = graph.getModel().getParent(tmp);
								 
								 if (graph.getModel().isVertex(tmp))
								 {
									 graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
								 }
							 }
						 }, true);
				 }));
			 }
		 }), parent);
		 menu.addSeparator(parent);
		 menu.addItem(mxResources.get('organic'), null, mxUtils.bind(this, function()
		 {
			 var layout = new mxFastOrganicLayout(graph);
			 
			 promptSpacing(layout.forceConstant, mxUtils.bind(this, function(newValue)
			 {
				 layout.forceConstant = newValue;
				 
					 this.editorUi.executeLayout(function()
					 {
						 var tmp = graph.getSelectionCell();
						 
						 if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
						 {
							 tmp = graph.getDefaultParent();
						 }
						 
						 layout.execute(tmp);
						 
						 if (graph.getModel().isVertex(tmp))
						 {
							 graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
						 }
					 }, true);
			 }));
		 }), parent);
		 menu.addItem(mxResources.get('circle'), null, mxUtils.bind(this, function()
		 {
			 var layout = new mxCircleLayout(graph);
			 
				 this.editorUi.executeLayout(function()
				 {
					 var tmp = graph.getSelectionCell();
					 
					 if (tmp == null || graph.getModel().getChildCount(tmp) == 0)
					 {
						 tmp = graph.getDefaultParent();
					 }
					 
					 layout.execute(tmp);
					 
					 if (graph.getModel().isVertex(tmp))
					 {
						 graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
					 }
				 }, true);
		 }), parent);
	 })));
	 this.put('navigation', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ['home', '-', 'exitGroup', 'enterGroup', '-', 'expand', 'collapse', '-', 'collapsible'], parent);
	 })));
	 this.put('arrange', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ['toFront', 'toBack', '-'], parent);
		 this.addSubmenu('direction', menu, parent);
		 this.addMenuItems(menu, ['turn', '-'], parent);
		 this.addSubmenu('align', menu, parent);
		 this.addSubmenu('distribute', menu, parent);
		 menu.addSeparator(parent);
		 this.addSubmenu('navigation', menu, parent);
		 this.addSubmenu('insert', menu, parent);
		 this.addSubmenu('layout', menu, parent);
		 this.addMenuItems(menu, ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'clearWaypoints', 'autosize'], parent);
	 }))).isEnabled = isGraphEnabled;
	 this.put('insert', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ['insertLink', 'insertImage'], parent);
	 })));
	 this.put('view', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ((this.editorUi.format != null) ? ['formatPanel'] : []).
			 concat(['outline', 'layers', '-', 'pageView', 'pageScale', '-', 'scrollbars', 'tooltips', '-',
							 'grid', 'guides', '-', 'connectionArrows', 'connectionPoints', '-',
							 'resetView', 'zoomIn', 'zoomOut'], parent));
	 })));
	 // Two special dropdowns that are only used in the toolbar
	 this.put('viewPanels', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 if (this.editorUi.format != null)
		 {
			 this.addMenuItems(menu, ['formatPanel'], parent);
		 }
		 
		 this.addMenuItems(menu, ['outline'], parent);
	 })));
	 this.put('viewZoom', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ['resetView', '-'], parent);
		 var scales = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];
		 
		 for (var i = 0; i < scales.length; i++)
		 {
			 (function(scale)
			 {
				 menu.addItem((scale * 100) + '%', null, function()
				 {
					 graph.zoomTo(scale);
				 }, parent);
			 })(scales[i]);
		 }
 
		 this.addMenuItems(menu, ['-', 'fitWindow', 'fitPageWidth', 'fitPage', 'fitTwoPages', '-', 'customZoom'], parent);
	 })));
	 this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', '-', 'import', 'export', '-', 'pageSetup', 'print'], parent);
	 })));
	 this.put('edit', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ['undo', 'redo', '-', 'cut', 'copy', 'paste', 'delete', '-', 'duplicate', '-',
			 'editData', 'editTooltip', '-', 'editStyle', '-', 'edit', '-', 'editLink', 'openLink', '-',
			 'selectVertices', 'selectEdges', 'selectAll', 'selectNone', '-', 'lockUnlock']);
	 })));
	 this.put('extras', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'editDiagram']);
	 })));
	 this.put('help', new Menu(mxUtils.bind(this, function(menu, parent)
	 {
		 this.addMenuItems(menu, ['help', '-', 'about']);
	 })));
 };
 
 /**
	* Adds the label menu items to the given menu and parent.
	*/
 Menus.prototype.put = function(name, menu)
 {
	 this.menus[name] = menu;
	 
	 return menu;
 };
 
 /**
	* Adds the label menu items to the given menu and parent.
	*/
 Menus.prototype.get = function(name)
 {
	 return this.menus[name];
 };
 
 /**
	* Adds the given submenu.
	*/
 Menus.prototype.addSubmenu = function(name, menu, parent, label)
 {
	 var entry = this.get(name);
	 
	 if (entry != null)
	 {
		 var enabled = entry.isEnabled();
	 
		 if (menu.showDisabled || enabled)
		 {
			 var submenu = menu.addItem(label || mxResources.get(name), null, null, parent, null, enabled);
			 this.addMenu(name, menu, submenu);
		 }
	 }
 };
 
 /**
	* Adds the label menu items to the given menu and parent.
	*/
 Menus.prototype.addMenu = function(name, popupMenu, parent)
 {
	 var menu = this.get(name);
	 
	 if (menu != null && (popupMenu.showDisabled || menu.isEnabled()))
	 {
		 this.get(name).execute(popupMenu, parent);
	 }
 };
 
 /**
	* Adds a menu item to insert a table cell.
	*/
 Menus.prototype.addInsertTableCellItem = function(menu, parent)
 {
	 var graph = this.editorUi.editor.graph;
	 
	 this.addInsertTableItem(menu, mxUtils.bind(this, function(evt, rows, cols)
	 {
		 var table = (mxEvent.isControlDown(evt) || mxEvent.isMetaDown(evt)) ?
			 graph.createCrossFunctionalSwimlane(rows, cols) :
			 graph.createTable(rows, cols, null, null,
			 (mxEvent.isShiftDown(evt)) ? 'Table' : null);
		 var pt = (mxEvent.isAltDown(evt)) ? graph.getFreeInsertPoint() :
			 graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([table], true));
		 var select = graph.importCells([table], pt.x, pt.y);
		 
		 if (select != null && select.length > 0)
		 {
			 graph.scrollCellToVisible(select[0]);
			 graph.setSelectionCells(select);
		 }
	 }), parent);
 };	
 
 /**
	* Adds a menu item to insert a table.
	*/
 Menus.prototype.addInsertTableItem = function(menu, insertFn, parent)
 {
	 insertFn = (insertFn != null) ? insertFn : mxUtils.bind(this, function(evt, rows, cols)
	 {
		 var graph = this.editorUi.editor.graph;
		 var td = graph.getParentByName(mxEvent.getSource(evt), 'TD');
 
		 if (td != null && graph.cellEditor.textarea != null)
		 {
			 var row2 = graph.getParentByName(td, 'TR');
			 
			 // To find the new link, we create a list of all existing links first
				 // LATER: Refactor for reuse with code for finding inserted image below
			 var tmp = graph.cellEditor.textarea.getElementsByTagName('table');
			 var oldTables = [];
			 
			 for (var i = 0; i < tmp.length; i++)
			 {
				 oldTables.push(tmp[i]);
			 }
			 
			 // Finding the new table will work with insertHTML, but IE does not support that
			 graph.container.focus();
			 graph.pasteHtmlAtCaret(createTable(rows, cols));
			 
			 // Moves cursor to first table cell
			 var newTables = graph.cellEditor.textarea.getElementsByTagName('table');
			 
			 if (newTables.length == oldTables.length + 1)
			 {
				 // Inverse order in favor of appended tables
				 for (var i = newTables.length - 1; i >= 0; i--)
				 {
					 if (i == 0 || newTables[i] != oldTables[i - 1])
					 {
						 graph.selectNode(newTables[i].rows[0].cells[0]);
						 break;
					 }
				 }
			 }
		 }
	 });
	 
	 // KNOWN: Does not work in IE8 standards and quirks
	 var graph = this.editorUi.editor.graph;
	 var row2 = null;
	 var td = null;
	 
	 function createTable(rows, cols)
	 {
		 var html = ['<table>'];
		 
		 for (var i = 0; i < rows; i++)
		 {
			 html.push('<tr>');
			 
			 for (var j = 0; j < cols; j++)
			 {
				 html.push('<td><br></td>');
			 }
			 
			 html.push('</tr>');
		 }
		 
		 html.push('</table>');
		 
		 return html.join('');
	 };
	 
	 // Show table size dialog
	 var elt2 = menu.addItem('', null, null, parent, null, null, null, true);
	 
	 // Quirks mode does not add cell padding if cell is empty, needs good old spacer solution
	 var quirksCellHtml = '<img src="' + mxClient.imageBasePath + '/transparent.gif' + '" width="16" height="16"/>';
 
	 function createPicker(rows, cols)
	 {
		 var table2 = document.createElement('table');
		 table2.setAttribute('border', '1');
		 table2.style.borderCollapse = 'collapse';
		 table2.style.borderStyle = 'solid';
 
		 if (!mxClient.IS_QUIRKS)
		 {
			 table2.setAttribute('cellPadding', '8');
		 }
		 
		 for (var i = 0; i < rows; i++)
		 {
			 var row = table2.insertRow(i);
			 
			 for (var j = 0; j < cols; j++)
			 {
				 var cell = row.insertCell(-1);
				 
				 if (mxClient.IS_QUIRKS)
				 {
					 cell.innerHTML = quirksCellHtml;
				 }
			 }
		 }
		 
		 return table2;
	 };
 
	 function extendPicker(picker, rows, cols)
	 {
		 for (var i = picker.rows.length; i < rows; i++)
		 {
			 var row = picker.insertRow(i);
			 
			 for (var j = 0; j < picker.rows[0].cells.length; j++)
			 {
				 var cell = row.insertCell(-1);
				 
				 if (mxClient.IS_QUIRKS)
				 {
					 cell.innerHTML = quirksCellHtml;
				 }
			 }
		 }
		 
		 for (var i = 0; i < picker.rows.length; i++)
		 {
			 var row = picker.rows[i];
			 
			 for (var j = row.cells.length; j < cols; j++)
			 {
				 var cell = row.insertCell(-1);
				 
				 if (mxClient.IS_QUIRKS)
				 {
					 cell.innerHTML = quirksCellHtml;
				 }
			 }
		 }
	 };
	 
	 elt2.firstChild.innerHTML = '';
	 var picker = createPicker(5, 5);
	 elt2.firstChild.appendChild(picker);
	 
	 var label = document.createElement('div');
	 label.style.padding = '4px';
	 label.style.fontSize = Menus.prototype.defaultFontSize + 'px';
	 label.innerHTML = '1x1';
	 elt2.firstChild.appendChild(label);
	 
	 function mouseover(e)
	 {
		 td = graph.getParentByName(mxEvent.getSource(e), 'TD');		
		 var selected = false;
		 
		 if (td != null)
		 {
			 row2 = graph.getParentByName(td, 'TR');
			 var ext = (mxEvent.isMouseEvent(e)) ? 2 : 4;
			 extendPicker(picker, Math.min(20, row2.sectionRowIndex + ext), Math.min(20, td.cellIndex + ext));
			 label.innerHTML = (td.cellIndex + 1) + 'x' + (row2.sectionRowIndex + 1);
			 
			 for (var i = 0; i < picker.rows.length; i++)
			 {
				 var r = picker.rows[i];
				 
				 for (var j = 0; j < r.cells.length; j++)
				 {
					 var cell = r.cells[j];
					 
					 if (i == row2.sectionRowIndex &&
						 j == td.cellIndex)
					 {
						 selected = cell.style.backgroundColor == 'blue';
					 }
					 
					 if (i <= row2.sectionRowIndex && j <= td.cellIndex)
					 {
						 cell.style.backgroundColor = 'blue';
					 }
					 else
					 {
						 cell.style.backgroundColor = 'transparent';
					 }
				 }
			 }
		 }
		 
		 mxEvent.consume(e);
 
		 return selected;
	 };
	 
	 mxEvent.addGestureListeners(picker, null, null, mxUtils.bind(this, function (e)
	 {
		 var selected = mouseover(e);
		 
		 if (td != null && row2 != null && selected)
		 {
			 insertFn(e, row2.sectionRowIndex + 1, td.cellIndex + 1);
			 
			 // Async required to block event for elements under menu
			 window.setTimeout(mxUtils.bind(this, function()
			 {
				 this.editorUi.hideCurrentMenu();
			 }), 0);
		 }
	 }));
	 mxEvent.addListener(picker, 'mouseover', mouseover);
 };
 
 /**
	* Adds a style change item to the given menu.
	*/
 Menus.prototype.edgeStyleChange = function(menu, label, keys, values, sprite, parent, reset)
 {
	 return menu.addItem(label, null, mxUtils.bind(this, function()
	 {
		 var graph = this.editorUi.editor.graph;
		 graph.stopEditing(false);
		 
		 graph.getModel().beginUpdate();
		 try
		 {
			 var cells = graph.getSelectionCells();
			 var edges = [];
			 
			 for (var i = 0; i < cells.length; i++)
			 {
				 var cell = cells[i];
				 
				 if (graph.getModel().isEdge(cell))
				 {
					 if (reset)
					 {
						 var geo = graph.getCellGeometry(cell);
			 
						 // Resets all edge points
						 if (geo != null)
						 {
							 geo = geo.clone();
							 geo.points = null;
							 graph.getModel().setGeometry(cell, geo);
						 }
					 }
					 
					 for (var j = 0; j < keys.length; j++)
					 {
						 graph.setCellStyles(keys[j], values[j], [cell]);
					 }
					 
					 edges.push(cell);
				 }
			 }
			 
			 this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', keys,
				 'values', values, 'cells', edges));
		 }
		 finally
		 {
			 graph.getModel().endUpdate();
		 }
	 }), parent, sprite);
 };
 
 /**
	* Adds a style change item to the given menu.
	*/
 Menus.prototype.styleChange = function(menu, label, keys, values, sprite, parent, fn, post)
 {
	 var apply = this.createStyleChangeFunction(keys, values);
	 
	 return menu.addItem(label, null, mxUtils.bind(this, function()
	 {
		 var graph = this.editorUi.editor.graph;
		 
		 if (fn != null && graph.cellEditor.isContentEditing())
		 {
			 fn();
		 }
		 else
		 {
			 apply(post);
		 }
	 }), parent, sprite);
 };
 
 /**
	* 
	*/
 Menus.prototype.createStyleChangeFunction = function(keys, values)
 {
	 return mxUtils.bind(this, function(post)
	 {
		 var graph = this.editorUi.editor.graph;
		 graph.stopEditing(false);
		 
		 graph.getModel().beginUpdate();
		 try
		 {
			 var cells = graph.getSelectionCells();
			 
			 for (var i = 0; i < keys.length; i++)
			 {
				 graph.setCellStyles(keys[i], values[i], cells);
 
				 // Removes CSS alignment to produce consistent output
				 if (keys[i] == mxConstants.STYLE_ALIGN)
				 {
					 graph.updateLabelElements(cells, function(elt)
					 {
						 elt.removeAttribute('align');
						 elt.style.textAlign = null;
					 });
				 }
				 
				 // Updates autosize after font changes
				 if (keys[i] == mxConstants.STYLE_FONTFAMILY)
				 {
					 for (var j = 0; j < cells.length; j++)
					 {
						 if (graph.model.getChildCount(cells[j]) == 0)
						 {
							 graph.autoSizeCell(cells[j], false);
						 }
					 }
				 }
			 }
			 
			 if (post != null)
			 {
				 post();
			 }
			 
			 this.editorUi.fireEvent(new mxEventObject('styleChanged',
				 'keys', keys, 'values', values, 'cells', cells));
		 }
		 finally
		 {
			 graph.getModel().endUpdate();
		 }
	 });
 };
 
 /**
	* Adds a style change item with a prompt to the given menu.
	*/
 Menus.prototype.promptChange = function(menu, label, hint, defaultValue, key, parent, enabled, fn, sprite)
 {
	 return menu.addItem(label, null, mxUtils.bind(this, function()
	 {
		 var graph = this.editorUi.editor.graph;
		 var value = defaultValue;
			 var state = graph.getView().getState(graph.getSelectionCell());
			 
			 if (state != null)
			 {
				 value = state.style[key] || value;
			 }
			 
		 var dlg = new FilenameDialog(this.editorUi, value, mxResources.get('apply'), mxUtils.bind(this, function(newValue)
		 {
			 if (newValue != null && newValue.length > 0)
			 {
				 graph.getModel().beginUpdate();
				 try
				 {
					 graph.stopEditing(false);
					 graph.setCellStyles(key, newValue);
				 }
				 finally
				 {
					 graph.getModel().endUpdate();
				 }
				 
				 if (fn != null)
				 {
					 fn(newValue);
				 }
			 }
		 }), mxResources.get('enterValue') + ((hint.length > 0) ? (' ' + hint) : ''));
		 this.editorUi.showDialog(dlg.container, 300, 80, true, true);
		 dlg.init();
	 }), parent, sprite, enabled);
 };
 
 /**
	* Adds a handler for showing a menu in the given element.
	*/
 Menus.prototype.pickColor = function(key, cmd, defaultValue)
 {
	 var graph = this.editorUi.editor.graph;
	 var h = 226 + ((Math.ceil(ColorDialog.prototype.presetColors.length / 12) +
			 Math.ceil(ColorDialog.prototype.defaultColors.length / 12)) * 17);
	 
	 if (cmd != null && graph.cellEditor.isContentEditing())
	 {
		 // Saves and restores text selection for in-place editor
		 var selState = graph.cellEditor.saveSelection();
		 
		 var dlg = new ColorDialog(this.editorUi, defaultValue || '000000', mxUtils.bind(this, function(color)
		 {
			 graph.cellEditor.restoreSelection(selState);
			 document.execCommand(cmd, false, (color != mxConstants.NONE) ? color : 'transparent');
		 }), function()
		 {
			 graph.cellEditor.restoreSelection(selState);
		 });
		 this.editorUi.showDialog(dlg.container, 230, h, true, true);
		 dlg.init();
	 }
	 else
	 {
		 if (this.colorDialog == null)
		 {
			 this.colorDialog = new ColorDialog(this.editorUi);
		 }
	 
		 this.colorDialog.currentColorKey = key;
		 var state = graph.getView().getState(graph.getSelectionCell());
		 var color = 'none';
		 
		 if (state != null)
		 {
			 color = state.style[key] || color;
		 }
		 
		 if (color == 'none')
		 {
			 color = 'ffffff';
			 this.colorDialog.picker.fromString('ffffff');
			 this.colorDialog.colorInput.value = 'none';
		 }
		 else
		 {
			 this.colorDialog.picker.fromString(color);
		 }
	 
		 this.editorUi.showDialog(this.colorDialog.container, 230, h, true, true);
		 this.colorDialog.init();
	 }
 };
 
 /**
	* Adds a handler for showing a menu in the given element.
	*/
 Menus.prototype.toggleStyle = function(key, defaultValue)
 {
	 var graph = this.editorUi.editor.graph;
	 var value = graph.toggleCellStyles(key, defaultValue);
	 this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', [key], 'values', [value],
			 'cells', graph.getSelectionCells()));
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.addMenuItem = function(menu, key, parent, trigger, sprite, label)
 {
	 var action = this.editorUi.actions.get(key);
 
	 if (action != null && (menu.showDisabled || action.isEnabled()) && action.visible)
	 {
		 var item = menu.addItem(label || action.label, null, function()
		 {
			 action.funct(trigger);
		 }, parent, sprite, action.isEnabled());
		 
		 // Adds checkmark image
		 if (action.toggleAction && action.isSelected())
		 {
			 menu.addCheckmark(item, Editor.checkmarkImage);
		 }
 
		 this.addShortcut(item, action);
		 
		 return item;
	 }
	 
	 return null;
 };
 
 /**
	* Adds a checkmark to the given menuitem.
	*/
 Menus.prototype.addShortcut = function(item, action)
 {
	 if (action.shortcut != null)
	 {
		 var td = item.firstChild.nextSibling.nextSibling;
		 var span = document.createElement('span');
		 span.style.color = 'gray';
		 mxUtils.write(span, action.shortcut);
		 td.appendChild(span);
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.addMenuItems = function(menu, keys, parent, trigger, sprites)
 {
	 for (var i = 0; i < keys.length; i++)
	 {
		 if (keys[i] == '-')
		 {
			 menu.addSeparator(parent);
		 }
		 else
		 {
			 this.addMenuItem(menu, keys[i], parent, trigger, (sprites != null) ? sprites[i] : null);
		 }
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.createPopupMenu = function(menu, cell, evt)
 {
	 menu.smartSeparators = true;
	 
	 this.addPopupMenuHistoryItems(menu, cell, evt);
	 this.addPopupMenuEditItems(menu, cell, evt);
	 this.addPopupMenuStyleItems(menu, cell, evt);
	 this.addPopupMenuArrangeItems(menu, cell, evt);
	 this.addPopupMenuCellItems(menu, cell, evt);
	 this.addPopupMenuSelectionItems(menu, cell, evt);
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.addPopupMenuHistoryItems = function(menu, cell, evt)
 {
	 if (this.editorUi.editor.graph.isSelectionEmpty())
	 {
		 this.addMenuItems(menu, ['undo', 'redo'], null, evt);
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.addPopupMenuEditItems = function(menu, cell, evt)
 {
	 if (this.editorUi.editor.graph.isSelectionEmpty())
	 {
		 this.addMenuItems(menu, ['pasteHere'], null, evt);
	 }
	 else
	 {
		 this.addMenuItems(menu, ['delete', '-', 'cut', 'copy', '-', 'duplicate'], null, evt);
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.addPopupMenuStyleItems = function(menu, cell, evt)
 {
	 if (this.editorUi.editor.graph.getSelectionCount() == 1)
	 {
		 this.addMenuItems(menu, ['-', 'setAsDefaultStyle'], null, evt);
	 }
	 else if (this.editorUi.editor.graph.isSelectionEmpty())
	 {
		 this.addMenuItems(menu, ['-', 'clearDefaultStyle'], null, evt);
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.addPopupMenuArrangeItems = function(menu, cell, evt)
 {
	 var graph = this.editorUi.editor.graph;
	 
	 if (!graph.isSelectionEmpty())
	 {
		 this.addMenuItems(menu, ['-', 'toFront', 'toBack'], null, evt);
	 }	
 
	 if (graph.getSelectionCount() > 1)	
	 {
		 this.addMenuItems(menu, ['-', 'group'], null, evt);
	 }
	 else if (graph.getSelectionCount() == 1 && !graph.getModel().isEdge(cell) &&
		 !graph.isSwimlane(cell) && graph.getModel().getChildCount(cell) > 0)
	 {
		 this.addMenuItems(menu, ['-', 'ungroup'], null, evt);
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.addPopupMenuCellItems = function(menu, cell, evt)
 {
	 var graph = this.editorUi.editor.graph;
	 cell = graph.getSelectionCell();
	 var state = graph.view.getState(cell);
	 menu.addSeparator();
	 
	 if (state != null)
	 {
		 var hasWaypoints = false;
 
		 if (graph.getModel().isEdge(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_EDGE, null) != 'entityRelationEdgeStyle' &&
			 mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) != 'arrow')
		 {
			 var handler = graph.selectionCellsHandler.getHandler(cell);
			 var isWaypoint = false;
			 
			 if (handler instanceof mxEdgeHandler && handler.bends != null && handler.bends.length > 2)
			 {
				 var index = handler.getHandleForEvent(graph.updateMouseEvent(new mxMouseEvent(evt)));
				 
				 // Configures removeWaypoint action before execution
				 // Using trigger parameter is cleaner but have to find waypoint here anyway.
				 var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
				 rmWaypointAction.handler = handler;
				 rmWaypointAction.index = index;
 
				 isWaypoint = index > 0 && index < handler.bends.length - 1;
			 }
			 
			 menu.addSeparator();
			 this.addMenuItem(menu, 'turn', null, evt, null, mxResources.get('reverse'));
			 this.addMenuItems(menu, [(isWaypoint) ? 'removeWaypoint' : 'addWaypoint'], null, evt);
			 
			 // Adds reset waypoints option if waypoints exist
			 var geo = graph.getModel().getGeometry(cell);
			 hasWaypoints = geo != null && geo.points != null && geo.points.length > 0;
		 }
 
		 if (graph.getSelectionCount() == 1 && (hasWaypoints || (graph.getModel().isVertex(cell) &&
			 graph.getModel().getEdgeCount(cell) > 0)))
		 {
			 this.addMenuItems(menu, ['-', 'clearWaypoints'], null, evt);
		 }
	 
		 if (graph.getSelectionCount() == 1)
		 {
			 this.addMenuItems(menu, ['-', 'editData', 'editStyle'], null, evt);
	 
			 // Shows edit image action if there is an image in the style
			 if (graph.getModel().isVertex(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE, null) != null)
			 {
				 menu.addSeparator();
				 this.addMenuItem(menu, 'image', null, evt).firstChild.nextSibling.innerHTML = mxResources.get('editImage') + '...';
			 }
		 }
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.addPopupMenuSelectionItems = function(menu, cell, evt)
 {
	 if (this.editorUi.editor.graph.isSelectionEmpty())
	 {
		 this.addMenuItems(menu, ['-', 'selectVertices', 'selectEdges', 'selectAll'], null, evt);
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.createMenubar = function(container)
 {
	 var menubar = new Menubar(this.editorUi, container);
	 var menus = this.defaultMenuItems;
	 
	 for (var i = 0; i < menus.length; i++)
	 {
		 (mxUtils.bind(this, function(menu)
		 {
			 var elt = menubar.addMenu(mxResources.get(menus[i]), mxUtils.bind(this, function()
			 {
				 // Allows extensions of menu.funct
				 menu.funct.apply(this, arguments);
			 }));
			 
			 this.menuCreated(menu, elt);
		 }))(this.get(menus[i]));
	 }
 
	 return menubar;
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menus.prototype.menuCreated = function(menu, elt, className)
 {
	 if (elt != null)
	 {
		 className = (className != null) ? className : 'geItem';
		 
		 menu.addListener('stateChanged', function()
		 {
			 elt.enabled = menu.enabled;
			 
			 if (!menu.enabled)
			 {
				 elt.className = className + ' mxDisabled';
				 
				 if (document.documentMode == 8)
				 {
					 elt.style.color = '#c3c3c3';
				 }
			 }
			 else
			 {
				 elt.className = className;
				 
				 if (document.documentMode == 8)
				 {
					 elt.style.color = '';
				 }
			 }
		 });
	 }
 };
 
 /**
	* Construcs a new menubar for the given editor.
	*/
 function Menubar(editorUi, container)
 {
	 this.editorUi = editorUi;
	 this.container = container;
 };
 
 /**
	* Adds the menubar elements.
	*/
 Menubar.prototype.hideMenu = function()
 {
	 this.editorUi.hideCurrentMenu();
 };
 
 /**
	* Adds a submenu to this menubar.
	*/
 Menubar.prototype.addMenu = function(label, funct, before)
 {
	 var elt = document.createElement('a');
	 elt.className = 'geItem';
	 mxUtils.write(elt, label);
	 this.addMenuHandler(elt, funct);
	 
		 if (before != null)
		 {
			 this.container.insertBefore(elt, before);
		 }
		 else
		 {
			 this.container.appendChild(elt);
		 }
	 
	 return elt;
 };
 
 /**
	* Adds a handler for showing a menu in the given element.
	*/
 Menubar.prototype.addMenuHandler = function(elt, funct)
 {
	 if (funct != null)
	 {
		 var show = true;
		 
		 var clickHandler = mxUtils.bind(this, function(evt)
		 {
			 if (show && elt.enabled == null || elt.enabled)
			 {
				 this.editorUi.editor.graph.popupMenuHandler.hideMenu();
				 var menu = new mxPopupMenu(funct);
				 menu.div.className += ' geMenubarMenu';
				 menu.smartSeparators = true;
				 menu.showDisabled = true;
				 menu.autoExpand = true;
				 
				 // Disables autoexpand and destroys menu when hidden
				 menu.hideMenu = mxUtils.bind(this, function()
				 {
					 mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
					 this.editorUi.resetCurrentMenu();
					 menu.destroy();
				 });
 
				 var offset = mxUtils.getOffset(elt);
				 menu.popup(offset.x, offset.y + elt.offsetHeight, null, evt);
				 this.editorUi.setCurrentMenu(menu, elt);
			 }
			 
			 mxEvent.consume(evt);
		 });
		 
		 // Shows menu automatically while in expanded state
		 mxEvent.addListener(elt, 'mousemove', mxUtils.bind(this, function(evt)
		 {
			 if (this.editorUi.currentMenu != null && this.editorUi.currentMenuElt != elt)
			 {
				 this.editorUi.hideCurrentMenu();
				 clickHandler(evt);
			 }
		 }));
		 
		 // Hides menu if already showing and prevents focus
				 mxEvent.addListener(elt, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
					 mxUtils.bind(this, function(evt)
		 {
			 show = this.currentElt != elt;
			 evt.preventDefault();
		 }));
 
		 mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt)
		 {
			 clickHandler(evt);
			 show = true;
		 }));
	 }
 };
 
 /**
	* Creates the keyboard event handler for the current graph and history.
	*/
 Menubar.prototype.destroy = function()
 {
	 // do nothing
 };
 
 /**
	* Constructs a new action for the given parameters.
	*/
 function Menu(funct, enabled)
 {
	 mxEventSource.call(this);
	 this.funct = funct;
	 this.enabled = (enabled != null) ? enabled : true;
 };
 
 // Menu inherits from mxEventSource
 mxUtils.extend(Menu, mxEventSource);
 
 /**
	* Sets the enabled state of the action and fires a stateChanged event.
	*/
 Menu.prototype.isEnabled = function()
 {
	 return this.enabled;
 };
 
 /**
	* Sets the enabled state of the action and fires a stateChanged event.
	*/
 Menu.prototype.setEnabled = function(value)
 {
	 if (this.enabled != value)
	 {
		 this.enabled = value;
		 this.fireEvent(new mxEventObject('stateChanged'));
	 }
 };
 
 /**
	* Sets the enabled state of the action and fires a stateChanged event.
	*/
 Menu.prototype.execute = function(menu, parent)
 {
	 this.funct(menu, parent);
 };
 
 /**
	* "Installs" menus in EditorUi.
	*/
 EditorUi.prototype.createMenus = function()
 {
	 return new Menus(this);
 };
 